Dify 1.17 升级实测(三):循环内人工审批与图片直传实测——两个高频场景的新解法
📖 摘要:1.17 把两个高频交付场景的能力补齐了:人工输入表单(HITL)终于能在 Iteration 循环内逐项暂停、逐项确认——审批流可以直接做了;图片直传多模态模型也打通了——上传、文件变量、vision 配置、视觉模型识别一条链路。但这两块都有新格式和新坑:迭代节点 DSL 大改、output_selector 收集格式、文件访问控制。本文是完整实测记录。
一、实验目的:两个被问过很多次的场景
做交付这些年,被问得最多的两个需求:
- 审批流:一批任务,每一项都要人工确认,能不能在循环里逐项暂停等确认?
- 图片理解:用户传一张图,让模型看——怎么传?哪个模型能看?
1.16 时代这两个需求都「能凑合」:审批流靠拆解成多次调用,图片得先转 URL 再想办法塞进提示词。1.17 把两个都做成了原生能力。本文实测这两条链路,验证它们到底能不能直接用于交付。
二、场景设计:两个贴近业务的实验
实验 A(审批流):输入「任务A,任务B」,工作流用 Iteration 遍历每项任务,每项暂停弹人工确认表单,确认后继续下一项,最终输出每项的人工确认结果数组。
实验 B(图片理解):用户上传一张测试图片(蓝色背景 + 左上角红色方块),LLM 节点配视觉模型 qwen-vl-plus,直传图片让它描述内容。
三、整体架构:两条链路
四、迭代内人工输入(HITL in Iteration)
4.1 先说迭代格式:1.17 大改
实验第一件事就撞上了破坏性变更——1.17 的 iteration 节点格式和 1.16 完全不同:
# 1.17 的迭代节点(实测格式)
- id: iter_check
data:
type: iteration
start_node_id: iter_start # 迭代开始节点 ID
iterator_selector: ["cd_items", "items"] # 遍历的数组
output_selector: ["cd_build", "result"] # 收集每次迭代的输出(节点.字段)
is_parallel: false
parallel_nums: 10
error_handle_mode: terminated
flatten_output: true
# 迭代开始节点(类型也变了)
- id: iter_start
data:
type: iteration-start # 1.16 是 custom-iteration-start三个变化:items 变
iterator_selector、output 变
output_selector +
start_node_id、迭代开始标记类型变了。迭代内节点不再需要
isInIteration/iteration_id/parentId 标记——范围由
start_node_id 界定,这是 1.17 的简化。
4.2 迭代内放人工输入:逐项暂停
工作流结构:code 节点生成数组 → Iteration 遍历 → 迭代内是「人工确认表单」→「组装结果」→ 迭代输出。
人工输入节点格式与 1.16 兼容(type=human-input / form_content /
inputs / user_actions / timeout),迭代内引用当前项用
{{#iter_check.item#}}:
- id: hi_confirm
data:
type: human-input
title: 逐项确认
form_content: "请确认任务「{{#iter_check.item#}}」:\n{{#confirm#}}"
inputs:
- type: paragraph
label: 确认意见
required: true
output_variable_name: confirm
default: {type: constant, value: "同意执行"}
user_actions:
- {id: approve, title: 同意, button_style: primary}
- {id: reject, title: 拒绝, button_style: default}
timeout: 1
timeout_unit: hour4.3 运行与恢复:每次迭代独立暂停
运行后工作流在第一个 human-input 处暂停(status=paused,elapsed 约 0.7 秒)。恢复链路三步:
1. GET /console/api/workflow/{run_id}/pause-details
→ paused_nodes[0].pause_type.form_id
2. 查数据库拿表单令牌(API 不给!):
select access_token from human_input_form_recipients where form_id='...'
3. POST /console/api/form/human_input/{form_token}
{"inputs": {"confirm": "同意执行任务A"}, "action": "approve"}
提交后工作流恢复,进入第二次迭代再次暂停——每次迭代一个独立表单。两项都确认后,流程跑完:
{"result": ["任务A|同意任务1", "任务B|同意任务2"]}逐项确认结果正确收集。1.16 时代人工输入恢复时的竞态 bug(Client response stream closed)在 1.17 未复现——两次暂停恢复全稳定。
4.4 迭代输出收集的坑
output_selector 是本次实验最隐蔽的坑:必须写
[节点id, 字段]
格式(["cd_build", "result"])。我们第一版写成了输出变量名
["iter_result"]——运行成功、不报错,但迭代输出数组全是
null。静默失败,验收时容易漏。
五、多模态图片直传
5.1 三个配置点
# 1. 开始节点的文件变量
- id: start
data:
type: start
variables:
- label: 图片
type: file
variable: image
required: true
allowed_file_types: ["image"] # 分类枚举:image/document/audio/video/custom
# 2. LLM 节点开 vision
- id: llm_v
data:
type: llm
model: {provider: langgenius/tongyi/tongyi, name: qwen-vl-plus, mode: chat}
prompt_template:
- role: user
text: "请描述这张图片的内容,用中文。"
vision:
enabled: true
configs:
variable_selector: ["start", "image"]
detail: high注意 allowed_file_types
是分类枚举(image/document/audio/video/custom),不是
MIME 类型——写 image/png 直接 400。
5.2 上传链路:必须走 service API
图片上传有个访问控制的坑:console
后台的上传接口(/console/api/files/upload)传的文件,service
API 运行时引用会报 Invalid upload file——两套体系隔离。
正确姿势:用应用密钥走 service API 上传:
POST /v1/files/upload (Authorization: Bearer 应用密钥,multipart 表单)
→ 拿到 upload_file_id
运行传参(file 变量是单个对象,不是数组):
{"image": {"type": "image", "transfer_method": "local_file", "upload_file_id": "..."}}
还有个隐蔽约束:文件绑定上传时的应用——同一个文件换了应用(重新导入的 app)引用就失效,每个被测应用要独立上传。
5.3 运行结果
测试图是程序生成的:蓝色背景 + 左上角红色方块。qwen-vl-plus 识别结果(3.9 秒):
这张图片非常简洁,主要由两种颜色构成:蓝色和红色。整体布局:大部分区域被纯蓝色填充;左上角有一个小的红色正方形……红色正方形在蓝色背景的衬托下显得格外突出。
颜色、位置、布局描述全部准确——图片直传链路完整可用。
六、运行验证汇总
| 验证项 | 方法 | 结果 |
|---|---|---|
| 迭代内逐项暂停 | 2 项数组 → 2 次独立暂停(form_id 不同) | ✓ |
| 恢复链路 | 表单提交 200 → 工作流恢复继续 | ✓ |
| 迭代输出收集 | output_selector 节点.字段格式 → 结果数组正确 | ✓ |
| 图片直传 | qwen-vl-plus 描述测试图(颜色/位置/布局) | ✓ |
| 文件访问控制 | console 上传文件 service 引用报错 → v1 上传通过 | ✓ |
七、实战坑:6 个坑
| 坑 | 现象 | 修复 |
|---|---|---|
| 迭代 DSL 不兼容 | 1.16 格式导入 1.17 报错 | 按 4.1 新格式迁移 |
| output_selector 写错 | 迭代输出数组静默 null | 写 [节点id, 字段],验收断言非 null |
| form_token 拿不到 | pause-details 只给 form_id | DB 查 recipients 表 |
| 文件类型白名单 | 写 MIME(image/png)报 400 | 用分类枚举(image) |
| console 文件 service 不可见 | Invalid upload file | v1/files/upload 上传 |
| 文件跨应用失效 | 新 app 引用旧文件报错 | 每 app 独立上传 |
八、结论:两个能力都能直接交付
- 审批流:数组 + Iteration + human-input,天然支持逐项确认——「任务清单审批」「批量内容审核」这类需求可以直接做,不用再拆多次调用
- 图片理解:v1 上传 + file 变量 + vision 直传,链路完整——「图片描述」「文档视觉识别」「拍照报障」这类需求落地成本大幅下降
两个能力共同的提醒:1.17 的迭代格式是破坏性变更,存量迭代 DSL 要迁移;文件链路要按 service API 的规矩走。踩过这些坑,这两条链路就是顺手的能力。
📚 本系列其他篇:一、升级总览与 5 件事 | 二、Agent V2 节点与技能包实测
实验文档与源码获取
- 循环内人工输入实验记录(实验 04,含完整迭代 DSL)
- 多模态图片直传实验记录(实验 05,含完整 vision 配置)
- 相关 DSL 与应用导出:dify-108 实验工作区
本文基于 Dify 1.17.0 + Hermes Agent v0.21.0 实测,配置在不同版本间可能变化,使用前请确认版本。AI 参与创作声明:本文由 AI 辅助写作,内容基于作者真实实测记录。
- Dify Agent 应用实战:Beta 版「真 Agent」的能力边界实测
- Dify workflow 与 Hermes Agent skill 的确定性对比
- Dify 意图分类节点总翻车?从 33% 失败率到兜底不崩——可靠性与韧性的三层加固
- Dify 标注回复实战:让智能客服记住人工答案的纠错闭环
- Dify 知识库元数据过滤实战:检索噪声 75% 降到 0 的确定性闸门
- RAG 建库,如何自动设置分段模式
- RAG知识库,如何进行持续更新运维
- RAG知识库的元数据过滤能力边界
- 数据库里的结构化数据,怎么建立 RAG 知识库?两条路线与选型判断
- 流程卡在「等人审批」?把审批链接送到企业微信和邮箱
- Dify 1.17 升级实测(一):从工作流平台到 Agent 平台,升级前必须知道的 5 件事
- Dify 应用上架门户:分享页每次回答都挂着内部流程节点?一个字段关掉
- RAG 知识库交付实战(上):4277 页手册喂给 AI——从凌晨故障到三模块方案
- RAG 知识库建库前,数据到底该怎么清洗?一条可复用的清洗管线实测
- 我们的门户机器人,为什么用 Dify 答、不把 skill 搬上云端 Hermes?
- 知识库从需求到交付:清洗、入库、维护全流程,照着走、每一步都能验证
- Dify 1.17 升级实测(二):Agent V2 节点与技能包实测——配置在数据库,不在 DSL
- RAG 知识库交付实战(中):三大深坑与修复实录——流程图截断/限流风暴/并联污染
- DeepSeek 思考模式什么情况下可以关?一次空输出事故的排查实录
- Dify 1.17 升级实测(三):循环内人工审批与图片直传实测——两个高频场景的新解法
- Dify 定时触发(trigger-schedule)实测:工作流到点自动跑,和三个必须知道的坑
- Dify 知识库三种分段模式实测:通用、父子、Q&A 到底怎么选?
- Dify 知识库接入 Notion/网页:先搞清三件事,再谈清洗
- RAG 知识库交付实战(下):18 条用例与成本测算
- 知识库数据清洗后,怎么知道洗得干不干净?一套三层质量门禁实测
- Dify 实战:供应商报价单格式五花八门,AI 怎么知道哪列是单价?